import torch
from torch import nn


class Inception(nn.Module):
    def __init__(self, inplanes, outplanes):
        super(Inception, self).__init__()
        self.inplanes = inplanes
        self.outplanes = outplanes
        self.channels = self.inplanes // 4
        self.conv1_1 = nn.Conv2d(self.inplanes, self.channels * 2, kernel_size=1, stride=1)
        self.conv1_2 = nn.Conv2d(self.inplanes, self.outplanes // 4, kernel_size=1, stride=1)
        self.conv3_3 = nn.Conv2d(self.channels * 2, self.outplanes // 4, kernel_size=3, stride=1, padding=1)
        self.conv5_5 = nn.Conv2d(self.channels * 2, self.outplanes // 4, kernel_size=5, stride=1, padding=2)
        self.maxpooling_3_3 = nn.MaxPool2d((3, 3), 1, padding=(1, 1))
        self.bn = nn.BatchNorm2d(self.outplanes)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        out1 = self.conv1_2(x)
        out2 = self.conv3_3(self.conv1_1(x))
        out3 = self.conv5_5(self.conv1_1(x))
        out4 = self.conv1_2(self.maxpooling_3_3(x))
        out = torch.cat([out1, out2, out3, out4], axis=1)
        return self.relu(self.bn(out))


class SPP(nn.Module):
    def __init__(self, spp_inplanes):
        super(SPP, self).__init__()
        self.spp_inplanes = spp_inplanes
        self.avgpool_1_1 = nn.AdaptiveAvgPool2d(1)
        self.avgpool_2_2 = nn.AdaptiveAvgPool2d(2)
        self.avgpool_3_3 = nn.AdaptiveAvgPool2d(3)
        self.conv_1_1 = nn.Conv2d(self.spp_inplanes, self.spp_inplanes // 8, kernel_size=1)
        self.relu = nn.ReLU()

    def forward(self, x):
        spp_b1 = torch.flatten(self.conv_1_1(self.avgpool_1_1(x)),1)

        spp_b2 = torch.flatten(self.conv_1_1(self.avgpool_2_2(x)),1)

        spp_b3 = torch.flatten(self.conv_1_1(self.avgpool_3_3(x)),1)

        return self.relu(torch.cat([spp_b1, spp_b2, spp_b3], axis=1))


class BasicBlock(nn.Module):
    # 残差块定义
    extention = 1

    def __init__(self, inplanes, planes, stride, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride
        self.planes = planes

        self.alpha = nn.Parameter(torch.randn(1, planes, 1, 1))
        self.beta = nn.Parameter(torch.ones(1, planes, 1, 1) - self.alpha)
        self.alpha.requires_grad = True
        self.beta.requires_grad = True
        self.alpha.to("cuda")
        self.beta.to("cuda")
        #
        # self.a= nn.Parameter(torch.randn(1, planes, 1, 1))
        # self.b = nn.Parameter(torch.ones(1, planes, 1, 1) - self.a)
        # self.a.requires_grad = True
        # self.b.requires_grad = True
        # self.a.to("cuda")
        # self.b.to("cuda")
        #
        self.avgpool = nn.AvgPool2d((3, 3), 2, 1)
        self.maxpool = nn.MaxPool2d((3, 3), 2, 1)

    def forward(self, x):
        identity = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        if self.downsample is not None:
            # identity=self.downsample(x)
            identity = self.alpha*self.avgpool(self.downsample(x))+self.beta*self.maxpool(self.downsample(x))
            # identity = self.a * self.avgpool(self.downsample(x)) + self.b * self.maxpool(self.downsample(x))
        out += identity
        out = self.relu(out)
        return out


class ResNet18_PRO_Model(nn.Module):
    def __init__(self, block, layers, num_class, SPP):
        self.inplane = 64
        super(ResNet18_PRO_Model, self).__init__()
        self.SPP = SPP(512)
        self.block = block
        self.layers = layers

        self.conv1 = nn.Conv2d(3, self.inplane, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(self.inplane)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)


        self.layer1 = self.make_layer(self.block, 64, layers[0], stride=1)
        self.layer2 = self.make_layer(self.block, 128, layers[1], stride=2)
        self.layer3 = self.make_layer(self.block, 256, layers[2], stride=2)
        self.layer4 = self.make_layer(self.block, 512, layers[3], stride=2)

        self.fc = nn.Linear(896 * block.extention, num_class)
        # self.avgpool = nn.AvgPool2d((13,13))

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.maxpool(out)
        # block部分
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.SPP(out)
        # out = self.avgpool(out)
        # out = torch.flatten(out,1)
        out = self.fc(out)
        return out

    def make_layer(self, block, plane, block_num, stride=1):
        block_list = []
        downsample = None
        if (stride != 1 or self.inplane != plane * block.extention):
            downsample = nn.Sequential(
                nn.Conv2d(self.inplane, plane * block.extention, stride=1, kernel_size=1, bias=False),
                nn.BatchNorm2d(plane * block.extention)
            )
        conv_block = block(self.inplane, plane, stride=stride, downsample=downsample)
        block_list.append(conv_block)
        self.inplane = plane * block.extention
        for i in range(1, block_num):
            block_list.append(block(self.inplane, plane, stride=1))
        return nn.Sequential(*block_list)


if __name__ == "__main__":
    resnet = ResNet18_PRO_Model(BasicBlock, [2, 2, 2, 2], 2, SPP)
    x = torch.randn(64, 3, 416, 416)
    x = resnet(x)
    print(x.shape)
